package ddz.net;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 客户端连接管理器
 *
 * @author zkpursuit
 */
public final class CtxManager {

    /**
     * socket通信channel的id，对应一个客户端
     */
    private static final AttributeKey<Object> bind_id = AttributeKey.valueOf("id");
    private static final AttributeKey<Object> bind_last_time = AttributeKey.valueOf("bind_last_time");
    private static final AttributeKey<Object> bind_online_time = AttributeKey.valueOf("bind_online_time");
    public static final AttributeKey<long[]> bind_received = AttributeKey.valueOf("bind_received");

    private final Map<Object, ChannelHandlerContext> clientMap;
    private static final CtxManager instance = new CtxManager();

    /**
     * 构造方法，不允许外部实例化
     */
    private CtxManager() {
        clientMap = new ConcurrentHashMap<>();
    }

    public static final CtxManager instance() {
        return instance;
    }

    public static final Object getBindId(ChannelHandlerContext ctx) {
        return ctx.channel().attr(bind_id).get();
    }

    /**
     * 移除客户端连接
     *
     * @param id 客户端连接唯一标识
     * @return 客户端连接管道
     */
    public ChannelHandlerContext remove(Object id) {
        if (clientMap.containsKey(id)) {
            return clientMap.remove(id);
        }
        return null;
    }

    public List<ChannelHandlerContext> channelList() {
        List<ChannelHandlerContext> list = new ArrayList<>(clientMap.size());
        clientMap.forEach((key, val) -> {
            list.add(val);
        });
        return list;
    }

    /**
     * 移除客户端连接
     *
     * @param ctx 客户端连接管道
     * @return socket信道绑定的ID
     */
    public Object remove(ChannelHandlerContext ctx) {
        Iterator<Map.Entry<Object, ChannelHandlerContext>> iter = clientMap.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<Object, ChannelHandlerContext> entry = iter.next();
            ChannelHandlerContext context = entry.getValue();
            if (context.equals(ctx)) {
                clientMap.remove(entry.getKey());
                Channel channel = ctx.channel();
                Object id = channel.attr(bind_id).getAndSet(null);
                channel.attr(bind_last_time).set(null);
                channel.attr(bind_online_time).set(null);
                return id;
            }
            Object id1 = entry.getKey();
            Object id2 = ctx.channel().attr(bind_id).get();
            if (id1.equals(id2)) {
                clientMap.remove(id1);
                Channel channel = ctx.channel();
                channel.attr(bind_id).set(null);
                channel.attr(bind_last_time).set(null);
                channel.attr(bind_online_time).set(null);
                return id2;
            }
        }
        return 0;
    }

    /**
     * 添加客户端连接
     *
     * @param id  客户端连接唯一标识
     * @param ctx 客户端连接管道
     */
    public void add(Object id, ChannelHandlerContext ctx) {
        remove(id);
        clientMap.put(id, ctx);
        ctx.channel().attr(bind_id).set(id);
    }

    /**
     * 客户端总数量
     *
     * @return
     */
    public int getClientCount() {
        return clientMap.size();
    }

    /**
     * 获取客户端连接管道
     *
     * @param id 客户端连接唯一标识
     * @return 客户端连接管道
     */
    public ChannelHandlerContext get(Object id) {
        return clientMap.get(id);
    }

    public void close() {
        clientMap.forEach((Object id, ChannelHandlerContext ctx) -> {
            ctx.channel().close();
        });
    }

    public void setLastTime(ChannelHandlerContext ctx, long millsecs) {
        ctx.channel().attr(bind_last_time).set(millsecs);
    }

    public long getLastTime(ChannelHandlerContext ctx) {
        Object obj = ctx.channel().attr(bind_last_time).get();
        if (obj != null) return (long) obj;
        return 0;
    }

    public void setOnlineTime(ChannelHandlerContext ctx, long millsecs) {
        ctx.channel().attr(bind_online_time).set(millsecs);
    }

    public long getOnlineTime(ChannelHandlerContext ctx) {
        Object obj = ctx.channel().attr(bind_online_time).get();
        if (obj != null) return (long) obj;
        return 0;
    }

}
